package graphics;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;

import javax.imageio.ImageIO;

import core.GameOptions;


/**
 * 
 * @author Anton
 *
 * Class for handling sprites.
 * Sprites are defined as a row of square images, width and height of each sprite are equal to the 
 * original spritemaps height. 
 * One additional row of pixels is added as information. This row is placed to the right in the image.
 * The topmost information pixel defines the color that is changed depending on player color.
 *
 */
public class SpriteHandler implements ImageObserver{

	private static final boolean printingEnabled = false;
	private static final boolean drawBox = false;
	private static SpriteHandler me;
	private static int creationCounter = 0;
	
	
	
	private List<String> spriteNames = new ArrayList<String>();
	//private List<List<BufferedImage>> images = new ArrayList<List<BufferedImage>>();
	/**
	 * Columns define different types of sprites, rows are preloaded color variations of the original sprites.
	 */
	private List<List<Sprite>> sprites = new ArrayList<List<Sprite>>();
	private List<ColorModel> cMod = new ArrayList<ColorModel>();
	
	public static SpriteHandler getInstance(){
		if (me==null){
			me=new SpriteHandler();
			creationCounter++;
			if(printingEnabled)
				System.out.println("Created "+creationCounter+" times");
		}
		
		return me;
	}
	private SpriteHandler(){
		loadSprites();
	}
	
	/**
	 * Loads all .png, .jpeg and .jpg images
	 */
	public void loadSprites(){
		String iPath=GameOptions.spriteFolder;
		
		try 
  		{
			for(String s : new File(iPath).list()){
				if(s.endsWith(".png") || s.endsWith(".jpeg") || s.endsWith(".jpg")){
					List<Sprite> newSpriteList = new ArrayList<Sprite>();
					newSpriteList.add( loadSprite(iPath+s) );
					sprites.add(newSpriteList);
					spriteNames.add(s);
				}	
			}	
			if(printingEnabled)
				System.out.println(sprites.size()+" images were successfully loaded!");
  		} catch (IOException e) {
  			System.out.println("WARNING! File load error!");
  		} catch (ConcurrentModificationException e){
  			System.out.println("WARNING! ConcurrentModificationException in ImageHandler. Check for solutions on thursday.");
  			System.out.println(e.toString());
  		}
	}
	
	public Sprite loadSprite(String filename) throws IOException, ConcurrentModificationException{
		
		Graphics tg;
		BufferedImage i1=ImageIO.read(new File(filename));
		BufferedImage i2;
		Sprite newSprite = new Sprite();
		int imageSize = i1.getHeight();
		int amountOfImages = i1.getWidth() / imageSize;
		for(int c = 0; c < amountOfImages; c++){
			i2=new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_ARGB); //Square image
			tg=i2.getGraphics();
			tg.setPaintMode();
			tg.setColor(new Color(0, 0, 0, 0));
			tg.fillRect(0, 0, imageSize, imageSize);
			tg.drawImage(i1.getSubimage(imageSize*c, 0, imageSize, imageSize), 0, 0, this);
			newSprite.addImage(i2);
		
		}
		System.out.println("Sprite "+filename+" contains "+newSprite.images.size() +" images.");
		return newSprite;
		
	}
	
	public int getIdOfSprite(String spriteName){
		return spriteNames.indexOf(spriteName);
	}
	
	public void drawSprite(int topLeftX, int topLeftY, int width, int height, double direction, int colorId, int spriteId, Graphics g){
		AffineTransform at = new AffineTransform();
		BufferedImage image;
		if(spriteId == -1)
			//imageId = imageNames.indexOf("imageNotFound.png");
			System.out.println("durp");
		if(spriteId != -1){
			image = sprites.get(spriteId).get(colorId).getImage(direction);
			at.translate(topLeftX, topLeftY);
			if(sprites.get(spriteId).get(colorId).isMirrored(direction)){
				at.scale(-1, 1);
				at.translate(-width, 0);
			}
			
			at.scale((double)width/image.getWidth(), (double)height/image.getHeight());
			
			
			//at.rotate(Math.PI, image.getWidth()/2, image.getHeight()/2);
			at.rotate(0.001, image.getWidth()/2, image.getHeight()/2);  // For some REALLY strange reason, something
																	// is buffered making rotated images faster
			
						
			Graphics2D g2d = (Graphics2D) g;
			g2d.drawImage(image, at, this);
			g2d.setColor(Color.red);
			//g2d.drawString(""+direction, topLeftX, topLeftY);
			
			if(drawBox){
				g.setColor(Color.red);
				g.drawRect(topLeftX, topLeftY, width, height);
			}
		}
	}
	
	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y,
			int width, int height) {
		// TODO Auto-generated method stub
		return false;
	}
	
	private class Sprite {
		protected List<BufferedImage> images = new ArrayList<BufferedImage>();
		protected Color color;
		
		protected Sprite(){
			
		}
		protected Sprite(Sprite baseSprite, Color replacedColor){
			
		}
		
		/**
		 * Images should be added in consistent order, down -> left -> up
		 * 
		 * @param image
		 */
		protected void addImage(BufferedImage image){
			images.add(image);
		}
		/**
		 * Returns the image of the given direction.
		 * @param direction
		 * @return
		 */
		protected BufferedImage getImage(double direction){
			double dirFromDown = direction-0.25+1;
			if(dirFromDown < 0){
				dirFromDown = 0;
			}
			if(images.size()%2 == 0) // Even number of images never mirrors.
				return images.get((int)(dirFromDown*images.size())%images.size());
			else{
				if(dirFromDown < 0.5){
					return images.get((int)(dirFromDown*images.size()*2)%images.size());
				}
				else{
					return images.get((int)((1-dirFromDown)*images.size()*2)%images.size());
				}
			}
				
		}
		protected boolean isMirrored(double direction){
			if(images.size()%2 == 0) // Even number of images never mirrors.
				return false;
			double dirFromDown = direction-0.25+1;
			return (dirFromDown > 0.5);
		}
		/**
		 * Specifies which color is to be variable
		 * @param c
		 */
		protected void specifyColor(Color c){
			color = color;
		}
		
		protected void changeColor(Color c){
			
		}
		
	}
	

}
